package qy.qyalgotrader.mystrategy.monitor;

import com.google.common.collect.Table;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.map.ListOrderedMap;
import qy.jalgotrade.bar.Bar;
import qy.jalgotrade.bar.Bars;
import qy.jalgotrade.barfeed.BaseBarFeed;
import qy.jalgotrade.broker.backtesting.BacktestingBroker;
import qy.jalgotrade.broker.backtesting.TradePercentage;
import qy.jalgotrade.broker.fillstrategy.DefaultStrategy;
import qy.jalgotrade.broker.fillstrategy.FillStrategy;
import qy.jalgotrade.dataseries.BarDataSeries;
import qy.jalgotrade.dataseries.DataSeries;
import qy.jalgotrade.dataseries.SequenceDataSeries;
import qy.jalgotrade.strategy.BacktestingStrategy;
import qy.jalgotrade.technical.ATR;
import qy.jalgotrade.technical.BollingerBands;
import qy.jalgotrade.technical.MACD;
import qy.jalgotrade.utils.CommonUtils;
import qy.qyalgotrader.mystrategy.StratUtils;
import qy.qyalgotrader.utils.CommonDataUtils;
import qy.qyalgotrader.utils.Constants;

import java.io.InputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;

/**
 *
 */
public class PortfolioValuationMonitor extends BacktestingStrategy {

	// @formatter:off
	public static final Map<String, Object> DEFAULT_OPT_PARAMS = MapUtils.putAll(new ListOrderedMap<>(), new Object[][] {
			{"signalFreq", null},
			{"bBandsPeriod", 20}, {"numStdDev", 2.0}, // BOLL
			{"atrPeriod", 26}, // ATR
			{"fastEma", 12}, {"slowEma", 26}, {"signalEma", 9}, {"diffSignal", false}, // MACD
			{"useMaxDdSl", true}, {"maxDdAtr", 2.0}, {"maxDdSlAbs", false}, // 止盈 / 止损参数
			{"signalMode", StratUtils.MySignalMode.DEFAULT}, {"loggerOff", false}, {"useSseSzseTradeTime", false}, // 其他参数
			// 回测模拟 Broker 参数:
			{"cashOrBrokerParams", MapUtils.putAll(new ListOrderedMap<>(), new Object[][] {
					{"initialCash", 1000000},
					{"commission", 0.002,}, // 5 元(每笔)起收, 假设每笔交易 2500 元起
					{"allowNegativeCash", true},
					{"fillStrategy.noVolumeLimit", true}
			})},
			// 比较基准: 沪深 300
			{"benchmark", "000300.SH"}
	});

	/**
	 * 当前 /上一个 交易日:
	 */
	private final LocalDate __currTd; private final LocalDate __preTd; private final ZonedDateTime __initDateTime;

	/**
	 *
	 */
	private double __initPortfolioEquity;

	/**
	 *
	 */
	private double __initBenchmarkValue;

	/**
	 * 标的:
	 */
	private final List<String> __instruments;

	private final String __benchmark;

	private final SequenceDataSeries<Double> __portfolioReturn;

	private final SequenceDataSeries<Double> __benchmarkReturn;

	private Table<String, String, String> __initHoldings;

	/**
	 * 信号模式: 多 / 空 / 多空
	 */
	private StratUtils.MySignalMode __signalMode;

	/**
	 * 策略的信号时间周期, 即主时间周期:
	 */
	private Bar.Frequency __signalFreq;

	// BOLL:
	private int __bBandsPeriod; private double __numStdDev;

	// ATR:
	private int __atrPeriod;

	// MACD:
	private int __fastEma; private int __slowEma; private int __signalEma;
	@SuppressWarnings("unused")
	private boolean __diffSignal;

	// 止盈 / 止损参数:
	private final boolean __useMaxDdSl; // 是否使用 最大回撤止盈 / 止损
	private final double __maxDdAtr;
	private final boolean __maxDdSlAbs; // 是否在头寸产生亏损时执行
	// @formatter:on

	private final Map<String, BollingerBands> bbandsDict;

	private final Map<String, ATR> atrDict;

	private final Map<String, MACD> macdDict;

	// 其他参数:
	private final boolean __loggerOff;

	private final boolean __useSseSzseTradeTime;

	public PortfolioValuationMonitor(BaseBarFeed barFeed, List<String> instruments)
			throws Exception {

		this(barFeed, instruments, DEFAULT_OPT_PARAMS);
	}

	public PortfolioValuationMonitor(BaseBarFeed barFeed, List<String> instruments, Map<String, Object> optParams)
			throws Exception {

		super();

		// 初始化回测 Broker:
		@SuppressWarnings("unchecked")
		Map<String, Object> cashOrBrokerParams = (Map<String, Object>) optParams.getOrDefault("cashOrBrokerParams",
				DEFAULT_OPT_PARAMS.get("cashOrBrokerParams"));
		BacktestingBroker broker = new BacktestingBroker((double) cashOrBrokerParams.get("initialCash"), barFeed,
				new TradePercentage((double) cashOrBrokerParams.get("commission")));
		broker.setAllowNegativeCash((boolean) cashOrBrokerParams.get("allowNegativeCash"));
		if ((boolean) cashOrBrokerParams.get("fillStrategy.noVolumeLimit")) {
			FillStrategy bfs = broker.getFillStrategy();
			((DefaultStrategy) bfs).setNoVolumeLimit();
		}

		_init(barFeed, broker);

		__currTd = CommonDataUtils.nowTradingDate();
		__preTd = CommonDataUtils.tradingDaysOffset(__currTd, -1);
		// 组合净值 (市值) 起始 DateTime: 上个交易日的 last-bar:
		__initDateTime = ZonedDateTime.of(__preTd, Constants.CLOSE_TIME_PM.minusSeconds(barFeed.getFrequency().getValue()),
				CommonUtils.MarketSession.SSE.getZoneId()).withZoneSameInstant(ZoneId.of("UTC"));
		__instruments = instruments; // 标的
		__benchmark = (String) optParams.getOrDefault("benchmark", DEFAULT_OPT_PARAMS.get("benchmark")); // 比较基准
		__portfolioReturn = new SequenceDataSeries<>(Double.class);
		__benchmarkReturn = new SequenceDataSeries<>(Double.class);
		// 信号模式: 多 / 空 / 多空:
		__signalMode = (StratUtils.MySignalMode) optParams.getOrDefault("singalMode", DEFAULT_OPT_PARAMS.get("singalMode"));
		// 策略的信号时间周期, 即主时间周期
		__signalFreq = (Bar.Frequency) optParams.getOrDefault("signalFreq", DEFAULT_OPT_PARAMS.get("signalFreq"));
		if (__signalFreq == null) {
			__signalFreq = getFeed().getFrequency();
		}
		// 指标参数:
		// BOLL:
		__bBandsPeriod = (int) optParams.getOrDefault("bBandsPeriod", DEFAULT_OPT_PARAMS.get("bBandsPeriod"));
		__numStdDev = (double) optParams.getOrDefault("numStdDev", DEFAULT_OPT_PARAMS.get("numStdDev"));
		// ATR:
		__atrPeriod = (int) optParams.getOrDefault("atrPeriod", DEFAULT_OPT_PARAMS.get("atrPeriod"));
		// MACD:
		__fastEma = (int) optParams.getOrDefault("fastEma", DEFAULT_OPT_PARAMS.get("fastEma"));
		__slowEma = (int) optParams.getOrDefault("slowEma", DEFAULT_OPT_PARAMS.get("slowEma"));
		__signalEma = (int) optParams.getOrDefault("signalEma", DEFAULT_OPT_PARAMS.get("signalEma"));
		__diffSignal = (boolean) optParams.getOrDefault("diffSignal", DEFAULT_OPT_PARAMS.get("diffSignal"));
		// 是否使用 最大回撤止盈 / 止损:
		__useMaxDdSl = (boolean) optParams.getOrDefault("useMaxDdSl", DEFAULT_OPT_PARAMS.get("useMaxDdSl"));
		__maxDdAtr = (double) optParams.getOrDefault("maxDdAtr", DEFAULT_OPT_PARAMS.get("maxDdAtr"));
		__maxDdSlAbs = (boolean) optParams.getOrDefault("maxDdSlAbs", DEFAULT_OPT_PARAMS.get("maxDdSlAbs"));

		bbandsDict = new HashMap<>();
		atrDict = new HashMap<>();
		macdDict = new HashMap<>();

		for (String e : __instruments) {
			bbandsDict.put(e,
					new BollingerBands(
							(SequenceDataSeries<Double>) ((BarDataSeries) barFeed.__getitem__(e)).getCloseDataSeries(),
							__bBandsPeriod, __numStdDev));
			atrDict.put(e, new ATR((BarDataSeries) barFeed.__getitem__(e), __atrPeriod));
			macdDict.put(e,
					new MACD((SequenceDataSeries<Double>) ((BarDataSeries) barFeed.__getitem__(e)).getCloseDataSeries(),
							__fastEma, __slowEma, __signalEma));
		}

		__loggerOff = (boolean) optParams.getOrDefault("loggerOff", DEFAULT_OPT_PARAMS.get("loggerOff"));
		__useSseSzseTradeTime = (boolean) optParams.getOrDefault("useSseSzseTradeTime",
				DEFAULT_OPT_PARAMS.get("useSseSzseTradeTime"));
	}

	/**
	 *
	 * @param csvIn csvIn
	 * @throws Exception Exception
	 */
	public void initHoldings(InputStream csvIn) throws Exception {

		__initHoldings = CommonUtils.readCsv(csvIn, "WindCode");
		BacktestingBroker broker = (BacktestingBroker) getBroker();
		// 添加持仓及可用资金:
		for (String instrument : __initHoldings.rowKeySet()) {
			if (!Arrays.asList(__benchmark, "cash", "repo", "total").contains(instrument)) {
				double q = Double.parseDouble(__initHoldings.get(instrument, "数量"));
				double price = Double.parseDouble(__initHoldings.get(instrument, "最新价"));
				if (!CommonUtils.fuzzyEqualZero(q)) {
					broker.setShares(instrument, q, price);
				}
			} else if (instrument.equals(__benchmark)) {
				__initBenchmarkValue = Double.parseDouble(__initHoldings.get(instrument, "最新价"));
			}
		}
		double cash = Double.parseDouble(__initHoldings.get("cash", "最新市值")) +
				Double.parseDouble(__initHoldings.get("repo", "最新市值"));
		broker.setCash(cash);
		// 初始化组合净值 (市值): 此时 barFeed 的 lastBar 为 null:
		__initPortfolioEquity = broker.getEquity();
//		__portfolioReturn.appendWithDateTime(__initDateTime, 0.0);
	}

	/**
	 *
	 * @return bbandsDict
	 */
	public Map<String, BollingerBands> getBbandsDict() {

		return bbandsDict;
	}

	/**
	 *
	 * @return atrDict
	 */
	public Map<String, ATR> getAtrDict() {

		return atrDict;
	}

	/**
	 *
	 * @return macdDict
	 */
	public Map<String, MACD> getMacdDict() {

		return macdDict;
	}

	/**
	 *
	 * @return __portfolioReturn
	 */
	public DataSeries<Double> getPortfolioReturnDs() {

		return __portfolioReturn;
	}

	/**
	 *
	 * @return __benchmarkReturn
	 */
	public DataSeries<Double> getBenchmarkReturnDs() {

		return __benchmarkReturn;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see qy.jalgotrade.strategy.BaseStrategy#onStart()
	 */
	@Override
	public void onStart() {

		if (!__loggerOff) {
			info("PortfolioValuationMonitor running started!");
			info(String.format("initial equity: %s, (positions: %.2f%%)", __initPortfolioEquity,
					(__initPortfolioEquity - getBroker().getCash()) / __initPortfolioEquity * 100));
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see qy.jalgotrade.strategy.BaseStrategy#onFinish(qy.jalgotrade.bar.Bars)
	 */
	@Override
	public void onFinish(Bars bars) {

		if (!__loggerOff) {
			info("PortfolioValuationMonitor running finished!");
		}
	}

	@Override
	public void onBars(Bars bars) throws Exception {

		// 若 BarDateTime 晚于开盘 Bar 时间, 则计算组合净值, 并更新
		LocalDateTime barsDm = CommonDataUtils.toMarketDatetime(bars.getDateTime()).toLocalDateTime();
		// 假设所有 instrument 交易时间段统一:
		if (!__useSseSzseTradeTime || tradeTimeFilter(bars)) {
			double equity = getBroker().getEquity();
			double portReturn = (equity - __initPortfolioEquity) / __initPortfolioEquity * 100;
			double bmPrice;
			if (bars.getBar(__benchmark) != null) {
				bmPrice = bars.getBar(__benchmark).getPrice();
			} else {
				bmPrice = getFeed().getDataSeries(__benchmark).getCloseDataSeries().__getitem__(-1);
			}
			double bmReturn = (bmPrice - __initBenchmarkValue) / __initBenchmarkValue * 100;
			__portfolioReturn.appendWithDateTime(bars.getDateTime(), portReturn);
			__benchmarkReturn.appendWithDateTime(bars.getDateTime(), bmReturn);
			if (!__loggerOff) {
				warning(String.format("portfolio equity return: %.2f%%; benchmark (%s) return: %.2f%%", portReturn, __benchmark, bmReturn));
			}
		}
	}

	/**
	 *
	 * @return is in trade time
	 */
	private boolean tradeTimeFilter(Bars bars) {

		LocalDateTime barsDm = CommonDataUtils.toMarketDatetime(bars.getDateTime()).toLocalDateTime();
		// TODO:
		return barsDm.compareTo(LocalDateTime.of(__currTd, Constants.OPEN_TIME_AM)) >= 0
				&& barsDm.compareTo(LocalDateTime.of(__currTd, Constants.CLOSE_TIME_PM)) < 0;
	}
}
